ElasticSearch 实现合并相似款商品

需求背景

商品列表页,有个合并相似款的筛选条件,何为相似款,商品同一档口,货号,价格,被称为相似款,原来已经有实现了合并功能,定时执行脚本,用mysql分组,查出合并后要显示的商品,设置缓存,es 构建的时候,读取缓存进行判断,存在设置合并字段为 1, 不存在设置为0,这样就可以为合并筛选提供查询条件,但是有个缺点,只能在全部商品中进行合并,不能根据条件进行筛选再合并,
偏偏产品经理就提出了要根据其它筛选条件进行合并的需求,这就意味着合并功能需要推翻重构,很麻烦,但天无绝人之路。

探索一,尝试直接用 ES 进行多字段分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

利用 aggs top_hits , 直接进行分组查询

GET index/type/_search
{
"query": {
"match": {}
},
"aggs": {
"group": {
"terms": {
"field": "group_field",
"size": 10
},
"aggs": {
"data": {
"top_hits": {}
}
}
}
}
}

问题:很明显,只能按一个字段进行分组,满足不了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

利用 script 实现多字段分组查询

GET /product/goods/_search
{
"size": 0,
"aggs": {
"group": {
"terms": {
"script": "doc['sid'].value + doc['shop_dangkou_no'].value + doc['item_price_pifa']"
},
"aggs": {
"data": {
"top_hits": {

}
}
}
}
},
"query": {
"bool": {
"must": [

]
}
}
}

问题:分组无法进行分页,大数据量分组查询占用内存较高

探索二, 尝试使用字段折叠功能

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"query" : {

},
"collapse" : {
"field" : "same_goods.keyword"
}
}


经实践,完美的解决了分组,分页功能

问题:返回的total是query的总数,无法得知字段折叠后的总数量,分页还是有问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

分组统计一下

{
"query" : {

},
"aggs": {
"group": {
"cardinality": {
"field": "same_goods.keyword"
}
}
}

}

完美,解决总数量分页的问题

方案落实

到此,利用字段折叠功能,可实现多字段合并,只需要构建一个字段,数据结构如下:

1
2
3
same_goods: 12345678:23456543:34456765

把相似的商品ID用冒号拼接,就可以用字段折叠筛选,后面如果有其它业务,需要知道宝贝的相似款,也方便查询

前台功能解决了,那如何构建这个字段呢

mysql 只能分组无法,把相似款的商品都查出来,只能用程序,要根据档口,货号,价格字段进行分组,然后拼成上面的数据格式,设置到redis并设置过期时间定时更新,key为当前的商品ID, 只设置有相似款的宝贝,其它没有相似款的宝贝不需要设置

好了代码撸完,准备上线… 问题来了…

问题:一条sql查出所有上架的商品,执行时间十几秒,宝贝表几百万数据,进行全表扫描,实在没办法10分钟跑一次勉强也能接受,最忍无可忍的就是,程序遍历了一百多万的数据赋值给新数组,脚本直接把内存给跑满了,尝试优化脚本,效果不佳只能换种思路了

优化方案:

拆,以档口为单位进行查询,档口ID可以并中索引,查询速度相当的快,查询数据量少,程序分组拼接数据结构,就不影响性能了,写一个合并的控制器接口,接口档口ID参数(数组形式批量处理,查几个档口速度还是很快的),收到参数后,再丢异步任务去执行,把档口的相似款设置到reids, 如果数据有变商品被下架了,key不再更新会自动过期,程序开了20个worker同时处理,每一次调用10个档口,再写一个定时执行的脚本,查询所有档口,批量调用合并接口,经测试2分钟就跑完所有档口,内存观察比原来节省了90%开销